package com.xiyuan.smartutils.control;

import com.xiyuan.smartutils.logging.LogFactory;
import com.xiyuan.smartutils.logging.Logger;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 线程扩展类，提供一套标准的循环操作的线程抽象类 基类实现如下： 1、线程管理（volatile的isRunning）、线程类thread，以及线程ID 2、不可重写的open/close方法 3、可读写的first/loop方法
 * <p>
 * 子类操作如下： 1、子类可提供线程组对象或线程名 2、子类可提供open/close操作前后的设置和清理实现 3、子类可提供可选的第一次回调和后续的循环回调实现
 *
 * @version v1.0.0 @author lgz 2018-11-17 新建与整理
 */
public abstract class Threadx {
    private static final Logger log = LogFactory.getLogger(Threadx.class);
    private CountDownLatch running;
    private Thread thread;
    private long threadId;
    
    private boolean isDaemon;
    
    /*************************************************************************/
    // 子类实现的方法
    /*************************************************************************/
    
    /**
     * 子类可选指定线程组
     */
    protected ThreadGroup getThreadGroup() {
        return null;
    }
    
    /**
     * 获取线程名称
     *
     * @return 县城名称
     */
    protected abstract String getThreadName();
    
    /**
     * 子类可选在线程开启之前的操作
     */
    protected boolean openBefore() {
        return true;
    }
    
    /**
     * 子类可选在线程开启之后的操作
     */
    protected boolean openAfter() {
        return true;
    }
    
    /**
     * 子类可选在线程关闭之前的操作
     */
    protected void closeBefore() {
    }
    
    /**
     * 子类可选在线程关闭之后的操作
     */
    protected void closeAfter() {
    }
    
    /**
     * 子类可选提供的第一次回调方法
     */
    protected void first() {
    }
    
    /**
     * 子类必须提供的循环回调方法
     */
    protected abstract void loop();
    
    /*************************************************************************/
    // 线程编号&状态
    /*************************************************************************/
    
    /**
     * 线程编号
     */
    public final long getThreadId() {
        return threadId;
    }
    
    /**
     * 线程是否运行
     */
    public final boolean isRunning() {
        return running != null && running.getCount() > 0;
    }
    
    /*************************************************************************/
    // 启动&关闭&运行
    
    /***********************************************************************/
    
    /**
     * Marks this thread as either a {@linkplain Thread#isDaemon daemon} thread
     * or a user thread. The Java Virtual Machine exits when the only
     * threads running are all daemon threads.
     *
     * <p> This method must be invoked before the thread is started.
     *
     * @param  isDaemon
     *         if {@code true}, marks this thread as a daemon thread
     *
     * @throws  IllegalThreadStateException
     *          if this thread is {@linkplain Thread#isAlive alive}
     *
 
     */
    public void setDaemon(boolean isDaemon) throws IllegalThreadStateException {
        this.isDaemon = isDaemon;
        if (thread != null) {this.thread.setDaemon(isDaemon);}
    }
    
    public final boolean isDaemon() {return isDaemon;}
    
    
    /**
     * 启动线程
     *
     * @return true|false
     */
    public final synchronized boolean start() {
        if (running != null && running.getCount() > 0) {
            return false;
        }
        
        try {
            // 1.开启线程之前的操作
            if (!openBefore()) {
                return false;
            }
            CountDownLatch startLatch = new CountDownLatch(1);
            CountDownLatch afterLatch = new CountDownLatch(1);
            CountDownLatch closeLatch = new CountDownLatch(1);
            AtomicBoolean started = new AtomicBoolean(true);
            // 2.开启线程
            running = closeLatch;
            thread = new Thread(getThreadGroup(), () -> {
                startLatch.countDown();
                
                // 1. 运行
                if (!isRunning()) {
                    return;
                }
                // 检查运行后
                try {
                    afterLatch.await();
                    if (!started.get()) {return;}// 没有启动成功
                }
                catch (Throwable e) {
                    return;
                }
                
                // 2. 运行开始
                try {
                    first();
                }
                catch (Throwable e) {}
                
                // 3.循环调用
                while (isRunning()) {
                    try {
                        loop();
                    }
                    catch (Throwable e) {
                        log.error(e);
                    }
                }
                
                // 4.关闭前
                try {
                    closeBefore();
                }
                catch (Throwable e) {
                }
                
                // 5.关闭线程之后的操作
                try {
                    closeAfter();
                }
                catch (Throwable e) {
                }
                
            }, getThreadName());
            thread.setDaemon(this.isDaemon);
            thread.start();
            // 3.阻塞等待线程启动成功
            startLatch.await();
            threadId = thread.getId();
            // 4.开启线程之后的操作
            try {
                started.set(openAfter());
                return started.get();
            }
            catch (Throwable e) {
                started.set(false);
                throw e;
            }
            finally {
                afterLatch.countDown();
                if (!started.get()) {
                    try {
                        close();
                    }
                    catch (Throwable e2) {}
                }
            }
        }
        catch (Throwable e) {
            log.error(e);
            this.running = null;
            if (thread != null) {
                try {
                    thread.interrupt();
                }
                catch (Throwable e2) {}
                thread = null;
            }
            return false;
        }
        
    }
    
    /**
     * 关闭
     */
    public final void close() {
        if (running != null) {
            running.countDown();
        }
        running = null;
    }
}
